﻿/******************************************************************************
 * SunnyUI 开源控件库、工具类库、扩展类库、多页面开发框架。
 * CopyRight (C) 2012-2022 ShenYongHua(沈永华).
 * QQ群：56829229 QQ：17612584 EMail：SunnyUI@QQ.Com
 *
 * Blog:   https://www.cnblogs.com/yhuse
 * Gitee:  https://gitee.com/yhuse/SunnyUI
 * GitHub: https://github.com/yhuse/SunnyUI
 *
 * SunnyUI.Common.dll can be used for free under the MIT license.
 * If you use this code, please keep this note.
 * 如果您使用此代码，请保留此说明。
 ******************************************************************************
 * 文件名称: ReflectionDynamicObject.cs
 * 文件说明: 反射动态对象
 * 当前版本: V3.1
 * 创建日期: 2022-03-02
 *
 * 2022-03-02: V3.1.1 增加文件说明
******************************************************************************/

//引用地址：https://www.cnblogs.com/Soar1991/p/15949370.html
//通过反射修改类私有变量
/*  示例
    public class MyApi
    {
        private DateTime _createdAt;
        public int ShowTimes { get; private set; }
    }

    var api = new MyApi();
    dynamic wrapper = ReflectionDynamicObject.Wrap(api);
    wrapper._createdAt = new DateTime(2022, 2, 2, 22, 22, 22);
    wrapper.ShowTimes = 100;
*/

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Reflection;

namespace Sunny.UI
{
#if NET45_OR_GREATER
    public sealed class ReflectionDynamicObject : DynamicObject
    {
        private readonly object _instance;
        private readonly Accessor _accessor;

        private ReflectionDynamicObject(object instance)
        {
            _instance = instance ?? throw new ArgumentNullException(nameof(instance));
            _accessor = GetAccessor(instance.GetType());
        }
        public static ReflectionDynamicObject Wrap(Object value)
        {
            if (value == null) throw new ArgumentNullException(nameof(value));
            return new ReflectionDynamicObject(value);
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (_accessor.TryFindGetter(binder.Name, out var getter))
            {
                result = getter.Get(_instance);
                return true;
            }

            return base.TryGetMember(binder, out result);
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            if (_accessor.TryFindSetter(binder.Name, out var setter))
            {
                setter.Set(_instance, value);
                return true;
            }

            return base.TrySetMember(binder, value);
        }

        #region 快速反射
        private interface IGetter
        {
            object Get(object instance);
        }
        private interface ISetter
        {
            void Set(object instance, object value);
        }

        private class Getter : IGetter
        {
            private FieldInfo _field;
            public Getter(FieldInfo field)
            {
                _field = field ?? throw new ArgumentNullException(nameof(field));
            }
            public object Get(object instance)
            {
                return _field.GetValue(instance);
            }
        }

        private class Setter : ISetter
        {
            private FieldInfo _field;
            public Setter(FieldInfo field)
            {
                _field = field ?? throw new ArgumentNullException(nameof(field));
            }
            public void Set(object instance, object value)
            {
                _field.SetValue(instance, value);
            }
        }

        private class Getter<T1, T2> : IGetter
        {
            private readonly Func<T1, T2> _getter;
            public Getter(Func<T1, T2> getter)
            {
                _getter = getter ?? throw new ArgumentNullException(nameof(getter));
            }
            public object Get(object instance)
            {
                return _getter((T1)instance);
            }
        }

        private class Setter<T1, T2> : ISetter
        {
            private readonly Action<T1, T2> _setter;
            public Setter(Action<T1, T2> setter)
            {
                this._setter = setter ?? throw new ArgumentNullException(nameof(setter));
            }
            public void Set(object instance, object value)
            {
                this._setter.Invoke((T1)instance, (T2)value);
            }
        }

        private class Accessor
        {
            public Accessor(Type type)
            {
                this._type = type ?? throw new ArgumentNullException(nameof(_type));
                var getter = new SortedDictionary<string, IGetter>();
                var setter = new SortedDictionary<string, ISetter>();

                var fields = _type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

                foreach (var field in fields)
                {
                    getter[field.Name] = new Getter(field);
                    setter[field.Name] = new Setter(field);
                }

                var props = _type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

                foreach (var item in props)
                {
                    if (item.CanRead)
                    {
                        var method = item.GetMethod;
                        var funcType = typeof(Func<,>).MakeGenericType(item.DeclaringType, item.PropertyType);
                        var func = method.CreateDelegate(funcType);
                        var getterType = typeof(Getter<,>).MakeGenericType(item.DeclaringType, item.PropertyType);
                        var get = (IGetter)Activator.CreateInstance(getterType, func);
                        getter[item.Name] = get;
                    }
                    if (item.CanWrite)
                    {
                        var method = item.SetMethod;
                        var actType = typeof(Action<,>).MakeGenericType(item.DeclaringType, item.PropertyType);
                        var act = method.CreateDelegate(actType);
                        var setterType = typeof(Setter<,>).MakeGenericType(item.DeclaringType, item.PropertyType);
                        var set = (ISetter)Activator.CreateInstance(setterType, act);
                        setter[item.Name] = set;
                    }
                }

                _getters = getter;
                _setters = setter;
            }

            private readonly Type _type;
            private readonly IReadOnlyDictionary<string, IGetter> _getters;
            private readonly IReadOnlyDictionary<string, ISetter> _setters;

            public bool TryFindGetter(string name, out IGetter getter) => _getters.TryGetValue(name, out getter);
            public bool TryFindSetter(string name, out ISetter setter) => _setters.TryGetValue(name, out setter);
        }

        private static Dictionary<Type, Accessor> _accessors = new Dictionary<Type, Accessor>();
        private static object _accessorsLock = new object();

        private static Accessor GetAccessor(Type type)
        {
            if (_accessors.TryGetValue(type, out var accessor)) return accessor;
            lock (_accessorsLock)
            {
                if (_accessors.TryGetValue(type, out accessor)) return accessor;
                accessor = new Accessor(type);
                var temp = new Dictionary<Type, Accessor>(_accessors);
                temp[type] = new Accessor(type);
                _accessors = temp;
                return accessor;
            }
        }
        #endregion
    }
#endif
}
